package com.yance.fim.handler.service;


import com.google.gson.Gson;
import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.InvalidProtocolBufferException;
import com.yance.fim.core.enums.ResponseCode;
import com.yance.fim.utils.RateLimiter;
import com.yance.fim.utils.ThreadPoolExecutorWrapper;
import com.yance.fim.utils.Utility;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static com.yance.fim.core.enums.ResponseCode.ERROR_CODE_OVER_FREQUENCY;
import static com.yance.fim.core.enums.ResponseCode.SUCCESS_CODE;

public abstract class IMHandler<T> {
    protected static final Logger LOG = LoggerFactory.getLogger(IMHandler.class);
    private Method parseDataMethod;
    private Class dataCls;
    private static ThreadPoolExecutorWrapper threadPoolExecutorWrapper;
    private static final RateLimiter mLimitCounter = new RateLimiter(5, 100);

    @Retention(RetentionPolicy.RUNTIME)
    public @interface ActionMethod {
    }

    protected static String actionName;


    public static void init(ThreadPoolExecutorWrapper poolExecutorWrapper) {
        threadPoolExecutorWrapper = poolExecutorWrapper;
    }

    public IMHandler() {
        try {
            if (StringUtil.isNullOrEmpty(actionName)) {
                Class cls = getClass();
                while (cls.getSuperclass() != null) {
                    for (Method method : cls.getSuperclass().getDeclaredMethods()) {
                        if (method.getAnnotation(ActionMethod.class) != null) {
                            actionName = method.getName();
                            break;
                        }
                    }
                    if (StringUtil.isNullOrEmpty(actionName)) {
                        cls = cls.getSuperclass();
                    } else {
                        break;
                    }
                }
            }


            for (Method method : getClass().getDeclaredMethods()) {
                if (method.getName() == actionName && method.getParameterCount() == 6) {
                    dataCls = method.getParameterTypes()[4];
                    break;
                }
            }

            if (dataCls.getSuperclass().equals(GeneratedMessage.class) || dataCls.getSuperclass().equals(GeneratedMessageV3.class)) {
                parseDataMethod = dataCls.getMethod("parseFrom", byte[].class);
            } else if (dataCls.isPrimitive()) {

            } else {

            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            Utility.printExecption(LOG, e);
        }
    }

    private T getDataObject(byte[] bytes) throws IllegalAccessException, InvocationTargetException {
        if (parseDataMethod != null) {
            T object = (T) parseDataMethod.invoke(dataCls, bytes);
            return object;
        }

        if (dataCls == String.class) {
            String str = new String(bytes);
            return (T) str;
        }

        if (dataCls == Byte.class) {
            Byte b = bytes[0];
            return (T) b;
        }

        if (dataCls == Void.class) {
            return null;
        }
        return (T) (new Gson().fromJson(new String(bytes), dataCls));
    }


    public ResponseCode preAction(String clientID, String fromUser, String topic, IMAckCallback callback) {
        LOG.info("imHandler fromUser={}, clientId={}, topic={}", fromUser, clientID, topic);
        if (!mLimitCounter.isGranted(clientID + fromUser + topic)) {
            ByteBuf ackPayload = Unpooled.buffer();
            ackPayload.ensureWritable(1).writeByte(ERROR_CODE_OVER_FREQUENCY.getCode());
            try {
                callback.onIMHandled(ERROR_CODE_OVER_FREQUENCY, ackPayload);
            } catch (Exception e) {
                Utility.printExecption(LOG, e);
            }
            return ERROR_CODE_OVER_FREQUENCY;
        }
        return SUCCESS_CODE;
    }

    public void doHandler(String clientID, String fromUser, String topic, byte[] payloadContent, IMAckCallback imAckCallback) {
        threadPoolExecutorWrapper.execute(() -> {
            IMAckCallback callbackWrapper = new IMAckCallback() {
                @Override
                public void onIMHandled(ResponseCode errorCode, ByteBuf ackPayload) {
                    LOG.debug("execute handler {} with result {}", this.getClass().getName(), errorCode);
                    //callback.onIMHandled(errorCode, ackPayload);
                    afterAction(clientID, fromUser, topic, imAckCallback);
                }
            };

            ResponseCode preActionCode = preAction(clientID, fromUser, topic, callbackWrapper);

            if (preActionCode == SUCCESS_CODE) {
                ByteBuf ackPayload = Unpooled.buffer(1);
                ResponseCode responseCode = SUCCESS_CODE;
                ackPayload.ensureWritable(1).writeByte(responseCode.getCode());

                try {
                    LOG.debug("execute handler for topic {}", topic);
                    responseCode = action(ackPayload, clientID, fromUser, getDataObject(payloadContent), callbackWrapper);
                } catch (IllegalAccessException e) {
                    Utility.printExecption(LOG, e);
                    responseCode = ResponseCode.ERROR_CODE_INVALID_DATA;
                } catch (InvocationTargetException e) {
                    Utility.printExecption(LOG, e);
                    responseCode = ResponseCode.ERROR_CODE_INVALID_DATA;
                } catch (Exception e) {
                    Utility.printExecption(LOG, e);
                    if (e instanceof InvalidProtocolBufferException) {
                        responseCode = ResponseCode.ERROR_CODE_INVALID_DATA;
                    } else {
                        responseCode = ResponseCode.ERROR_CODE_SERVER_ERROR;
                    }
                }

                response(ackPayload, responseCode, imAckCallback);
            } else {
                LOG.error("WsHandler {} preAction failure", this.getClass().getName());
                ByteBuf ackPayload = Unpooled.buffer(1);
                ackPayload.ensureWritable(1).writeByte(preActionCode.getCode());
                response(ackPayload, preActionCode, imAckCallback);
            }
        });
    }

    private void response(ByteBuf ackPayload, ResponseCode responseCode, IMAckCallback callback) {
        ackPayload.setByte(0, responseCode.getCode());
        try {
            callback.onIMHandled(responseCode, ackPayload);
        } catch (Exception e) {
            e.printStackTrace();
            Utility.printExecption(LOG, e);
        }
    }


    @ActionMethod
    abstract public ResponseCode action(ByteBuf ackPayload, String clientID, String fromUser, T request, IMAckCallback callback);


    public void afterAction(String clientID, String fromUser, String topic, IMAckCallback callback) {

    }

//    protected long saveAndPublish(String username, String clientID, FSCMessage.Message message) {
//        MessageResponse messageResponse = MessageResponse.convertMessageResponse(message);
//        messageService.storeMessage(username, clientID, messageResponse);
//        Set<String> notifyReceivers = messageService.getNotifyReceivers(username, messageResponse);
//        LOG.info("notifyReceivers {}",notifyReceivers);
//        int pullType = 0;
//        switch (message.getConversation().getType()){
//            case ProtoConstants.ConversationType.ConversationType_Private:
//                pullType = ProtoConstants.PullType.Pull_Normal;
//                break;
//            case ProtoConstants.ConversationType.ConversationType_Group:
//                pullType = ProtoConstants.PullType.Pull_Normal;
//                break;
//            case ProtoConstants.ConversationType.ConversationType_ChatRoom:
//                pullType = ProtoConstants.PullType.Pull_ChatRoom;
//                break;
//            case ProtoConstants.ConversationType.ConversationType_Channel:
//                break;
//
//        }
//        publisher.publish2Receivers(message, notifyReceivers, clientID, pullType);
//        return notifyReceivers.size();
//    }
}
